package org.codefilarete.stalactite.dsl.entity;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable;

import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
import org.codefilarete.reflection.ValueAccessPoint;
import org.codefilarete.stalactite.dsl.MappableSuperClassConfiguration;
import org.codefilarete.stalactite.dsl.PolymorphismPolicy;
import org.codefilarete.stalactite.dsl.RelationalMappingConfiguration;
import org.codefilarete.stalactite.dsl.embeddable.EmbeddableMappingConfiguration;
import org.codefilarete.stalactite.dsl.idpolicy.IdentifierPolicy;
import org.codefilarete.stalactite.dsl.key.CompositeKeyMappingConfigurationProvider;
import org.codefilarete.stalactite.dsl.naming.AssociationTableNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.ElementCollectionTableNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.JoinColumnNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.MapTableNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.TableNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.UniqueConstraintNamingStrategy;
import org.codefilarete.stalactite.sql.ddl.Size;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.stalactite.sql.result.ColumnedRow;
import org.codefilarete.tool.collection.ReadOnlyIterator;

import static org.codefilarete.tool.Nullable.nullable;

/**
 * Defines elements needed to configure a mapping of an entity class
 * 
 * @author Guillaume Mary
 */
public interface EntityMappingConfiguration<C, I> extends MappableSuperClassConfiguration<C>, RelationalMappingConfiguration<C> {
	
	@Nullable
	EntityFactoryProvider<C, ?> getEntityFactoryProvider();
	
	/**
	 * Gives the table owning entity data.
	 * @return may return null, then a table name will be deduced from entity name and {@link TableNamingStrategy}
	 */
	@Nullable
	Table getTable();
	
	@Nullable
	TableNamingStrategy getTableNamingStrategy();
	
	@Nullable
	ColumnNamingStrategy getColumnNamingStrategy();
	
	KeyMapping<C, I> getKeyMapping();
	
	EmbeddableMappingConfiguration<C> getPropertiesMapping();
	
	@Nullable
	OptimisticLockOption<C, ?> getOptimisticLockOption();
	
	/**
	 * Returns the parent configuration. Information is taken on the {@link #getInheritanceConfiguration()} instance.
	 * <strong>Be aware that the caller may take the {@link InheritanceConfiguration#isJoiningTables()} option
	 * into account for a complete behavior</strong>
	 * 
	 * @return the configuration of a parent type
	 */
	@Nullable
	@Override
	default EntityMappingConfiguration<? super C, I> getMappedSuperClassConfiguration() {
		return nullable(getInheritanceConfiguration()).map(InheritanceConfiguration::getParentMappingConfiguration).get();
	}
	
	/** Gives inheritance information if inheritance has been defined, else returns null */
	@SuppressWarnings("squid:S1452" /* Can't remove wildcard here because it requires to create a local generic "super" type which is forbidden */)
	@Nullable
	InheritanceConfiguration<C, I> getInheritanceConfiguration();
	
	@Nullable
	ForeignKeyNamingStrategy getForeignKeyNamingStrategy();
	
	@Nullable
	UniqueConstraintNamingStrategy getUniqueConstraintNamingStrategy();
	
	@Nullable
	AssociationTableNamingStrategy getAssociationTableNamingStrategy();
	
	@Nullable
	ElementCollectionTableNamingStrategy getElementCollectionTableNamingStrategy();
	
	@Nullable
	JoinColumnNamingStrategy getJoinColumnNamingStrategy();
	
	/**
	 * Gives {@link ColumnNamingStrategy} for index column of one-to-many {@link List} association
	 * @return maybe null, {@link ColumnNamingStrategy#INDEX_DEFAULT} will be used instead
	 */
	@Nullable
	ColumnNamingStrategy getIndexColumnNamingStrategy();
	
	@Nullable
	MapTableNamingStrategy getMapTableNamingStrategy();
	
	PolymorphismPolicy<C> getPolymorphismPolicy();
	
	/**
	 * Returns an {@link Iterable} over all mapped super class configurations.
	 *
	 * @return an iterable for all inheritance configurations, including this
	 */
	@Override
	default Iterable<EntityMappingConfiguration<?, I>> inheritanceIterable() {
		// overridden to specialize the result
		Iterable<? extends MappableSuperClassConfiguration<?>> superIterable = MappableSuperClassConfiguration.super.inheritanceIterable();
		return () -> new ReadOnlyIterator<EntityMappingConfiguration<?, I>>() {
			private final Iterator<? extends MappableSuperClassConfiguration<?>> delegate = superIterable.iterator();
			
			@Override
			public boolean hasNext() {
				return delegate.hasNext();
			}
			
			@Override
			public EntityMappingConfiguration<?, I> next() {
				return (EntityMappingConfiguration<?, I>) delegate.next();
			}
		};
	}
	
	interface EntityFactoryProvider<C, T extends Table> {

		Function<ColumnedRow, C> giveEntityFactory(T table);
		
		boolean isIdentifierSetByFactory();
	}
	
	interface InheritanceConfiguration<E, I> {
		
		/** Entity configuration */
		EntityMappingConfiguration<E, I> getParentMappingConfiguration();
		
		/**
		 * Tells if the super configuration table must be stored on a separate table and joined to the current one
		 * @return true if the super configuration table must be stored on a separate table and joined to the current one
		 */
		boolean isJoiningTables();
	}
	
	interface KeyMapping<C, I> {
		
		ReadWritePropertyAccessPoint<C, I> getAccessor();
		
		boolean isSetByConstructor();
	}
	
	interface SingleKeyMapping<C, I> extends KeyMapping<C, I> {
		
		IdentifierPolicy<I> getIdentifierPolicy();
		
		ColumnLinkageOptions getColumnOptions();
		
		@Nullable
		String getFieldName();
	}
	
	interface CompositeKeyMapping<C, I> extends KeyMapping<C, I> {
		
		Consumer<C> getMarkAsPersistedFunction();
		
		Function<C, Boolean> getIsPersistedFunction();
		
		CompositeKeyMappingConfigurationProvider<I> getCompositeKeyMappingBuilder();
	}
	
	interface ColumnLinkageOptions {
		
		@Nullable
		String getColumnName();
		
		@Nullable
		Size getColumnSize();
		
	}
	
	interface CompositeKeyLinkageOptions {
		
		Map<ValueAccessPoint, String> getColumnsNames();
		
	}
}
